/*
 * @Author: c
 * @Date: 2024-01-08 07:20:59
 * @LastEditTime: 2024-04-01 05:34:00
 * @LastEditors: c
 * @Description: 
 * @FilePath: \music-web-backend\src\core\music\artist\artist.service.ts
 * https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */
import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { plainToClass } from 'class-transformer';
import { zip } from 'compressing';
import { Response } from 'express';
import { join } from 'path';
import { EXCEPTION } from 'src/common/const/constants';
import { ExportExcelDto } from 'src/common/dto/excel.dto';
import { IOption } from 'src/common/dto/option.dto';
import { PageResDto } from 'src/common/dto/page.dto';
import { GenderEnumDesc } from 'src/common/enums/gender.enum';
import { UserNameExistException, UserNameNotFoundException } from 'src/common/exception/business.exception';
import { CommonException } from 'src/common/exception/common.exception';
import { ExcelService } from 'src/common/service/excel.service';
import { FileInfo } from 'src/core/system/minio/entities/file_info.entity';
import { User } from 'src/core/system/user/entities/user.entity';
import { Brackets, DataSource, InsertResult, Repository, WhereExpressionBuilder } from 'typeorm';
import { AlbumService } from '../album/album.service';
import { AlbumPageQuery } from '../album/dto/search-album.dto';
import { Picture } from '../picture/entities';
import { PictorialEnum } from '../picture/enum';
import { SongPageQuery } from '../song/dto/search-song.dto';
import { SongService } from '../song/song.service';
import { ArtistExcelConst } from './const/excel.const';
import { DArtist } from './dao/artist.dao';
import { ArtistForm, ArtistPageQuery, FavoriteForm } from './dto';
import { Artist } from './entities/artist.entity';
import { ArtistExportVO } from './vo/artist.vo';

@Injectable()
export class ArtistService {


  constructor(
    @Inject(forwardRef(() => SongService))
    private readonly songService: SongService,
    @InjectRepository(Artist) private artistRepository: Repository<Artist>,
    private dataSource: DataSource,
    private dartist: DArtist,
    // private songService: SongService,
    private albumService: AlbumService,
  ) { }


  /**
  * 歌手-歌手所有专辑
  * @param pageDto 
  * @returns 
  */
  getArtistAlbumList(user: User, pageDto: AlbumPageQuery) {
    return this.albumService.getArtistAlbumList(user, pageDto)
  }

  /**
* 歌手-歌手所有歌曲
* @param pageDto 
* @returns 
*/
  getArtistSongList(user: any, pageDto: SongPageQuery) {
    return this.songService.getArtistSongList(user, pageDto)
  }

  /**
 * 歌手-删除-我收藏的歌手
 * @param songForm 
 * @returns 
 */
  deletefavorite(user: User, songForm: FavoriteForm) {
    this.dartist.deleteFavorite(user?.id, songForm)
  }
  /**
  * 歌手-新增-我收藏的歌手
  * @param songForm 
  * @returns 
  */
  createfavorite(user: User, songForm: FavoriteForm) {
    this.dartist.createFavorite(user?.id, songForm)
  }
  /**
* 歌手-我收藏的歌手
* @param pageDto 
* @returns 
*/
  async myfavorite(user: User, page: ArtistPageQuery) {
    const [data, total] = await this.dartist.myfavorite(user?.id, page);
    return PageResDto.result(data, total)
  }

  /**
   * 歌手-查询列表
   * @param param0 
   * @returns 
   */
  async findAll(query: ArtistPageQuery) {

    const [users, total] = await this.dartist.page(null, query)
    return PageResDto.result(users, total)
  }


  /**
 * 歌手-推荐歌手
 * @param param0 
 * @returns 
 */
  async getRecommendSingers(query: ArtistPageQuery) {

    const users = await this.dartist.getRecommendSingers(null, query)
    return users
  }

  /**
   * 歌手-下拉选项
   */
  async listArtistOptions(query: ArtistPageQuery) {
    const data = await this.findAll(query)
    return data.list.map<IOption<string, number>>((artist: Artist) => ({ label: artist.name, value: artist.id }))
  }


  /**
 * 歌手-修改
 * @param artistId 
 * @param artistForm 
 */
  async updateArtist(artistId: number, artistForm: ArtistForm) {
    const exsitArtist: Artist = await this.findOne(artistId);

    const artist: Artist = plainToClass(Artist, artistForm);
    const allPic = [...artistForm.pic, ...artistForm.pictorialList];
    const exsitPics = [...exsitArtist.pictures]
    //设置失效的图片
    exsitPics.forEach((dp, index) => {
      const deletedPic = allPic.find(pi => pi.id === dp.fileInfo.id)
      if (!deletedPic) {
        exsitPics[index].isActive = false
        const artist = new Artist()
        artist.id = artistId
        exsitPics[index].artist = artist
      }

    })

    //新增图片
    const pics = [...exsitArtist.pictures]
    allPic.forEach(p => {
      const picIndex = exsitPics.findIndex(pi => pi.fileInfo.id === p.id)
      if (picIndex === -1) {
        const newP = plainToClass(Picture, p)
        newP.picType = p.picType

        newP.fileInfo = new FileInfo(p.id)
        exsitPics.push(newP)
      }
    })

    artist.pictures = exsitPics
    artist.pic = artistForm.pic.at(0).url
    return await this.artistRepository.save(artist);


    // await this.artistRepository
    //   .createQueryBuilder()
    //   .update(Artist)
    //   .set({ ...artistForm, pic: artistForm.pic[0].url })
    //   .where('id = :id', { id: artistId })
    //   .execute()

  }





  /**
  * 导入歌手列表
  * @param file 
  */
  async importArtists(file: Express.Multer.File) {

    const res = await ExcelService.import(file)

    //建立连接 创建runner
    const queryRunner = this.dataSource.createQueryRunner();
    queryRunner.connect();
    //开启事务
    await queryRunner.startTransaction();

    try {

      const artistList: Artist[] = plainToClass(Artist, res.map(({ roleDesc, ...rest }) => {

        return { ...rest, gender: rest.gender === GenderEnumDesc.MAN ? 1 : 2, email: rest.email.text }
      }))

      const insertArtistsResult: InsertResult = await queryRunner.manager.
        createQueryBuilder()
        .insert()
        .into(Artist)
        .values(artistList)
        .execute();

      // 现在提交事务：
      await queryRunner.commitTransaction()
    } catch (error) {
      Logger.error(error)
      await queryRunner.rollbackTransaction()
      throw new CommonException(EXCEPTION.COMMON)
    } finally {
      await queryRunner.release()
    }

    return res
  }

  /**
   * 歌手-新增
   * @param artistForm 
   * @returns 
   */
  async saveArtist(req, artistForm: ArtistForm): Promise<Artist> {
    const quser: Artist = await this.findOneByArtistName(artistForm.name)

    if (quser?.name === artistForm.name) {
      throw new UserNameExistException(EXCEPTION.USER.EXIST);

    }

    const avatar = artistForm.pic.at(0)
    const artist: Artist = plainToClass(Artist, artistForm);
    artist.pictures = plainToClass(Picture, artistForm.pictorialList.map(p => {

      const picture = new Picture(p.url, PictorialEnum.PICTORIAL)
      const fileinfo = new FileInfo(p.id)
      // fileinfo.id = `${req.user.username} ${p.uid} ${p.name}`
      picture.fileInfo = fileinfo
      return picture
    }))

    const avatarP = new Picture(avatar.url, PictorialEnum.AVATAR)
    const fileinfo = new FileInfo(avatar.id)
    // fileinfo.id = `${req.user.username} ${avatar.uid} ${avatar.name}`
    avatarP.fileInfo = fileinfo
    artist.pictures.push(avatarP)
    artist.pic = avatar.url
    return await this.artistRepository.save(artist);


  }

  /**
   * 歌手-批量删除
   * @param ids 
   */

  async deleteArtists(ids: string) {
    const idList = ids.split(',')


    // create a new query runner
    // establish real database connection using our new query runner
    const queryRunner = this.dataSource.createQueryRunner()

    queryRunner.connect()
    // lets now open a new transaction:
    await queryRunner.startTransaction()

    // execute some operations on this transaction:
    try {
      for await (const id of idList) {

        await queryRunner.manager.createQueryBuilder()
          .delete()
          .from(Artist)
          .where("id = :id", { id })
          .execute();
      }

      // commit transaction now:
      await queryRunner.commitTransaction()
    } catch (error) {
      // since we have errors let's rollback changes we made
      await queryRunner.rollbackTransaction()
      throw new CommonException(EXCEPTION.COMMON)
    } finally {
      // you need to release query runner which is manually created:
      await queryRunner.release()
    }

  }

  /**
   * 歌手-获取单条表单数据
   * @param artistId 
   */
  async getArtistFormData(artistId: number): Promise<ArtistForm> {
    const one = await this.findOne(artistId);

    if (!one) {
      throw new UserNameNotFoundException(EXCEPTION.USER.NOT_FOUND)
    }


    const data = plainToClass(ArtistForm, one);

    const avatar = one.pictures.find(p => p.picType == 1 && p.isActive)
    if (avatar) {
      // avatar.fileInfo.url = avatar.url
      data.pic = [{ ...avatar.fileInfo, url: avatar.url, picType: PictorialEnum.AVATAR }]
    } else {
      data.pic = []
    }
    data.pictorialList = one.pictures.filter(p => p.picType == 2 && p.isActive).map(p2 => {
      // p2.fileInfo.url = p2.url

      return ({ ...p2.fileInfo, url: p2.url, picType: p2.picType })
    })

    return data

  }


  async findOne(artistId: number): Promise<Artist> {
    // const artist: Artist = await this.artistRepository.findOne({ where: { id: artistId }, relations: ['pictures'] })

    const build = await this.artistRepository.createQueryBuilder("artist")
      .leftJoinAndSelect('artist.pictures', 'picture', `picture.isActive = 1`)
      .leftJoinAndSelect('picture.fileInfo', 'fileInfo')
      .where("artist.id = :artistId", { artistId: artistId })
    // .leftJoinAndSelect(Picture, 'picture', 'artist.id = picture.artist_id')
    // .leftJoinAndSelect(FileInfo, 'file_info', 'picture.file_id = file_info.id')
    //   .select(`
    //   artist.id as id,
    //   artist.name as name,
    //   artist.birthday as birthday,
    //   artist.region as region,
    //   artist.native_list as nativeList,
    //   artist.birth_place as birthPlace,
    //   artist.gender as gender,
    //   artist.pic as pic,
    //   artist.introduce as introduce,
    //   artist.weight as weight,
    //   artist.blood as blood,
    //   fileInfo.url as url,
    //   fileInfo.uid as uid,
    //   fileInfo.last_modified as lastModified,
    //   fileInfo.name as fileName
    // `)



    const one = await build.getOne();

    return one

  }


  async findOneByArtistName(userName: string): Promise<Artist> {

    const artist: Artist = await this.artistRepository.findOne({ where: { name: userName } })

    return artist;
  }


  async remove(id: number) {
    const deleteRes = await this.artistRepository.delete(id)
    return deleteRes;
  }


  /**
 * 导出歌手列表
 * @param userPage 
 */
  async exportArtists(res: Response, { pageNum = 1, pageSize = 10, keywords, status, startTime, endTime }: ArtistPageQuery): Promise<any> {

    const builder = this.artistRepository
      .createQueryBuilder('artist')
      .select(['artist.username as username',
        'artist.nickname as nickname',
        'artist.mobile as mobile',
        'artist.email as email',
        'artist.create_time as createTime',
        `CASE artist.gender
                WHEN 1 THEN '男'
                WHEN 2 THEN '女'
                ELSE '未知'
                END genderLabel`])
      .where('artist.username != :username', { username: 'root' })
      .andWhere('artist.deleted = :deleted', { deleted: 0 })
      .printSql();

    if (keywords) {
      builder.andWhere(new Brackets((qb: WhereExpressionBuilder) => {
        qb.where('artist.username like :keywords', { keywords: `%${keywords}%` })
          .orWhere('artist.nickname like :keywords', { keywords: `%${keywords}%` })
          .orWhere('artist.mobile like :keywords', { keywords: `%${keywords}%` })
      }))
    }

    if (status) {
      builder.andWhere('artist.status = :status', { status })
    }

    if (startTime) {
      builder.andWhere('artist.create_time >= :startTime', { startTime })
    }
    if (endTime) {
      builder.andWhere('artist.create_time <= :endTime', { endTime })
    }

    builder.groupBy("artist.id")



    const total = await builder.getCount()
    const users = await builder.offset((pageNum - 1) * pageSize).limit(pageSize).getRawMany();
    const userss: ArtistExportVO[] = plainToClass(ArtistExportVO, users)

    const exportExcelDto: ExportExcelDto<ArtistExportVO> = new ExportExcelDto()

    const keys = Object.keys(ArtistExcelConst).map(k => {
      return { header: ArtistExcelConst[k].desc, key: k, width: ArtistExcelConst[k].width }
    });



    exportExcelDto.columns = keys
    exportExcelDto.result = userss

    await ExcelService.export<ArtistExportVO>(res, exportExcelDto)
  }

  async downloadTemplate(res) {
    // const uploadDir = this.configService.get('file').root;
    const uploadDir = join(process.cwd(), 'dist/resources/歌手导入模板.xlsx')
    const tarStream = new zip.Stream();  //文件压缩流
    await tarStream.addEntry(uploadDir); //压缩文件路径

    res.setHeader('Content-Type', 'application/octet-stream');


    res.setHeader(
      'Content-Disposition',
      `attachment; filename=${encodeURI('歌手导入模板1.zip')}`,
    );

    tarStream.pipe(res)
    // return { filename: '歌手导入模板.xlsx', tarStream };
  }




}
